home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Plus Leser 19
/
Amiga Plus Leser CD 19.iso
/
Tools
/
Emulatoren
/
GameBoy
/
AmigaVGB
/
GB.c
< prev
next >
Wrap
C/C++ Source or Header
|
2000-07-07
|
18KB
|
583 lines
/** VGB: portable GameBoy emulator ***************************/
/** **/
/** GB.c **/
/** **/
/** This file contains the portable part of the GameBoy **/
/** hardware emulation. See GB.h for #defines related to **/
/** drivers and GameBoy hardware. **/
/** **/
/** Copyright (C) Marat Fayzullin 1995,1996 **/
/** You are not allowed to distribute this software **/
/** commercially. Please, notify me, if you make any **/
/** changes to this file. **/
/*************************************************************/
#include "GB.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
byte Verbose = 1; /* Verboseness level */
byte *RAM,*Page[8]; /* RAM and pointers to Z80 address space 8x8kB) */
int VPeriod = 6000; /* Number of Z80 cycles between VBlanks */
byte UPeriod = 1; /* Number of VBlanks per screen update */
byte LineDelay = 0; /* When 1, CMPLINE interrupts are delayed */
byte CheckCRC = 1; /* When 1, VGB checks cartridge CRC on loading */
byte AutoA = 0; /* When 1, autofire emulation for button A */
byte AutoB = 0; /* When 1, autofire emulation for button B */
char *SaveName = NULL; /* .sav file name */
char *SndName = NULL; /* Soundtrack log file */
FILE *SndStream = NULL;
struct /* Cheat list to be filled before StartGB() */
{ /* is called */
byte Value,Orig;
word Address;
} Cheats[MAXCHEAT];
int CheatCount = 0; /* Number of cheats in the list */
byte SIOBuf; /* Next byte to output via the serial line */
byte SIOFlag; /* Bit0 -> SIO interrupt should be generated */
/* Bit1 -> Data should be sent out */
byte JoyState; /* Joystick state: START.SELECT.B.A.D.U.L.R */
byte IMask; /* A mask to reset an event bit in IFLAGS */
byte MBCType; /* MBC type: 1 for MBC2, 0 for MBC1 */
byte *ROMMap[256]; /* Addresses of ROM banks */
byte ROMBank; /* Number of ROM bank currently used */
byte ROMMask; /* Mask for the ROM bank number */
int ROMBanks; /* Total number of ROM banks */
byte *RAMMap[256]; /* Addresses of RAM banks */
byte RAMBank; /* Number of RAM bank currently used */
byte RAMMask; /* Mask for the RAM bank number */
int RAMBanks; /* Total number of RAM banks */
unsigned long TCount,TStep; /* Timer counter and increment */
byte BPal[4]; /* Background palette */
byte SPal0[4],SPal1[4]; /* Sprite palettes */
byte *ChrGen; /* Character generator */
byte *BgdTab,*WndTab; /* Background and window character tables */
byte SprFlag; /* <>0: sprites were enabled during the frame */
/*** Following are some known manufacturer codes *************************/
struct { word Code;char *Name; } Companies[] =
{
{ 0x3301,"Nintendo" },{ 0x7901,"Accolade" },{ 0xA400,"Konami" },
{ 0x6701,"Ocean" },{ 0x5601,"LJN" },{ 0x9900,"ARC?" },
{ 0x0101,"Nintendo" },{ 0x0801,"Capcom" },{ 0x0100,"Nintendo" },
{ 0xBB01,"SunSoft" },{ 0xA401,"Konami" },{ 0xAF01,"Namcot?" },
{ 0x4901,"Irem" },{ 0x9C01,"Imagineer" },{ 0xA600,"Kawada?" },
{ 0xB101,"Nexoft" },{ 0x5101,"Acclaim" },{ 0x6001,"Titus" },
{ 0xB601,"HAL" },{ 0x3300,"Nintendo" },{ 0x0B00,"Coconuts?" },
{ 0x5401,"Gametek" },{ 0x7F01,"Kemco?" },{ 0xC001,"Taito" },
{ 0xEB01,"Atlus" },{ 0xE800,"Asmik?" },{ 0xDA00,"Tomy?" },
{ 0xB100,"ASCII?" },{ 0xEB00,"Atlus" },{ 0xC000,"Taito" },
{ 0x9C00,"Imagineer" },{ 0xC201,"Kemco?" },{ 0xD101,"Sofel?" },
{ 0x6101,"Virgin" },{ 0xBB00,"SunSoft" },{ 0xCE01,"FCI?" },
{ 0xB400,"Enix?" },{ 0xBD01,"Imagesoft" },{ 0x0A01,"Jaleco?" },
{ 0xDF00,"Altron?" },{ 0xA700,"Takara?" },{ 0xEE00,"IGS?" },
{ 0x8300,"Lozc?" },{ 0x5001,"Absolute?" },{ 0xDD00,"NCS?" },
{ 0xE500,"Epoch?" },{ 0xCB00,"VAP?" },{ 0x8C00,"Vic Tokai" },
{ 0xC200,"Kemco?" },{ 0xBF00,"Sammy?" },
{ 0x1800,"Hudson Soft" },{ 0xCA01,"Palcom/Ultra" },
{ 0xCA00,"Palcom/Ultra" },{ 0xC500,"Data East?" },
{ 0xA900,"Technos Japan?" },{ 0xD900,"Banpresto?" },
{ 0x7201,"Broderbund?" },{ 0x7A01,"Triffix Entertainment?" },
{ 0xE100,"Towachiki?" },{ 0x9300,"Tsuburava?" },
{ 0xC600,"Tonkin House?" },{ 0xCE00,"Pony Canyon" },
{ 0x7001,"Infogrames?" },{ 0x8B01,"Bullet-Proof Software?" },
{ 0x5501,"Park Place?" },{ 0xEA00,"King Records?" },
{ 0x5D01,"Tradewest?" },{ 0x6F01,"ElectroBrain?" },
{ 0xAA01,"Broderbund?" },{ 0xC301,"SquareSoft" },
{ 0x5201,"Activision?" },{ 0x5A01,"Bitmap Brothers/Mindscape" },
{ 0x5301,"American Sammy" },{ 0x4701,"Spectrum Holobyte" },
{ 0x1801,"Hudson Soft"},{ 0x0000,NULL }
};
void DoWrite(word A,byte V);
byte M_RDMEM(register word A)
{ return(Page[A>>13][A&0x1FFF]); }
void M_WRMEM(register word A,register byte V)
{
if(A>0xFEFF) DoWrite(A,V);
else if(A>0x7FFF) Page[A>>13][A&0x1FFF]=V;
else
switch(A&0xE000)
{
case 0xC000:
case 0x8000: RAM[A]=V;return;
case 0xA000: Page[5][A&0x1FFF]=V;return;
case 0xE000: DoWrite(A,V);return;
case 0x2000:
if(MBCType&&((A&0xFF00)!=0x2100)) return;
V&=ROMMask;
if(!V) V++;
if(ROMMask&&(V!=ROMBank))
{
ROMBank=V;
Page[2]=ROMMap[V]? ROMMap[V]:RAM+0x4000;
Page[3]=Page[2]+0x2000;
if(Verbose&0x08) printf("ROM: Bank %d selected\n",V);
}
return;
case 0x4000:
V&=RAMMask;
if(RAMMask&&!MBCType&&(RAMBank!=V))
{
RAMBank=V;
Page[5]=RAMMap[V]? RAMMap[V]:RAM+0xA000;
if(Verbose&0x08) printf("RAM: Bank %d selected\n",V);
}
return;
case 0x0000:
case 0x6000:
if(Verbose&0x02) printf("Wrote %Xh to ROM at %Xh\n",V,A);
return;
}
}
void DoWrite(register word A,register byte V)
{
static unsigned long TPFreqs[] = { 4096,262144,65536,16384 };
register byte *P;
switch(A)
{
case 0xFF00: JOYPAD=0xCF|V;
if(!(V&0x20)) JOYPAD&=(JoyState>>4)|0xF0;
if(!(V&0x10)) JOYPAD&=JoyState|0xF0;
return;
case 0xFF01: SIOBuf=V;SIOFlag|=0x02;return;
case 0xFF02: if(V&0x80)
{
if(SIOFlag&0x02? SIOSend(SIOBuf):SIOReceive(&SIODATA))
SIOFlag|=0x01;
SIOFlag=(SIOFlag&0xFD);
}
V|=0x7E;
break;
case 0xFF07: TStep=(TPFreqs[V&0x03]<<16)/(154*60);
V|=0xF8;break;
case 0xFF0F: V&=0x1F;break;
case 0xFFFF: V&=0x1F;break;
case 0xFF46: P=RAM+0xFE00;A=(word)V<<8;
for(V=0;V<0xA0;V++) *P++=M_RDMEM(A++);
return;
case 0xFF41: V=(V&0xF8)|(LCDSTAT&0x07);
break;
case 0xFF40: ChrGen=RAM+(V&0x10? 0x8000:0x8800);
BgdTab=RAM+(V&0x08? 0x9C00:0x9800);
WndTab=RAM+(V&0x40? 0x9C00:0x9800);
break;
case 0xFF44: V=0;break;
case 0xFF47: BPal[0]=V&0x03;
BPal[1]=(V&0x0C)>>2;
BPal[2]=(V&0x30)>>4;
BPal[3]=(V&0xC0)>>6;
break;
case 0xFF48: SPal0[0]=V&0x03;
SPal0[1]=(V&0x0C)>>2;
SPal0[2]=(V&0x30)>>4;
SPal0[3]=(V&0xC0)>>6;
break;
case 0xFF49: SPal1[0]=V&0x03;
SPal1[1]=(V&0x0C)>>2;
SPal1[2]=(V&0x30)>>4;
SPal1[3]=(V&0xC0)>>6;
break;
default: if((A>=0xFF10)&&(A<=0xFF26))
{
if(SndStream)
{ fputc(A-0xFF10,SndStream);fputc(V,SndStream); }
Sound(A-0xFF10,V);
}
}
RAM[A]=V;
}
int StartGB(char *CartName)
{
static char *CartTypes[] =
{
"ROM ONLY","ROM+MBC1","ROM+MBC1+RAM",
"ROM+MBC1+RAM+BATTERY","UNKNOWN",
"ROM+MBC2","ROM+MBC2+BATTERY"
};
int Checksum,I,J,*T;
FILE *F;
char *P,S[50];
word A;
reg R;
/*** STARTUP CODE starts here: ***/
T=(int *)"\01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
#ifdef LSB_FIRST
if(*T!=1)
{
puts("********** This machine is high-endian. *********");
puts("Take #define LSB_FIRST out and compile VGB again.");
return(0);
}
#else
if(*T==1)
{
puts("********* This machine is low-endian. *********");
puts("Insert #define LSB_FIRST and compile VGB again.");
return(0);
}
#endif
RAM=NULL;SaveName=NULL;SndStream=NULL;
for(I=0;I<256;I++) RAMMap[I]=ROMMap[I]=NULL;
if(Verbose) printf("Allocating 64kB for address space...");
if(!(RAM=malloc(0x10000))) { if(Verbose) puts("FAILED");return(0); }
memset(RAM,NORAM,0x10000);
for(I=0;I<8;I++) Page[I]=RAM+I*0x2000;
if(Verbose) printf("OK\nOpening %s...",CartName);
if(!(F=fopen(CartName,"rb")))
{ if(Verbose) puts("FAILED");return(0); }
if(Verbose) printf("reading...");
if(fread(RAM,1,0x4000,F)!=0x4000)
{ if(Verbose) puts("FAILED");return(0); }
ROMMap[0]=RAM;
ROMBanks=2<<RAM[0x0148];
RAMBanks=(RAM[0x0149]&0x03)==3? 4:0;
Checksum=((word)RAM[0x014E]<<8)+RAM[0x014F];
MBCType=RAM[0x0147]>3;
P=NULL;
if((RAM[0x0147]==4)||(RAM[0x0147]>6)) P="Unknown ROM type";
if(P)
{
printf("\nError loading cartridge: %s\n",P);
fclose(F);return(0);
}
if(Verbose)
{
strncpy(S,RAM+0x0134,16);S[16]='\0';
printf("OK\n Name: %s\n",S);
printf(" Type: %s\n",CartTypes[RAM[0x0147]]);
printf(" ROM Size: %dx16kB\n",ROMBanks);
J=(RAM[0x0149]&0x03)*2;J=J? (1<<(J-1)):0;
printf(" RAM Size: %dkB\n",J);
J=((word)RAM[0x014B]<<8)+RAM[0x014A];
for(I=0,P=NULL;!P&&Companies[I].Name;I++)
if(J==Companies[I].Code) P=Companies[I].Name;
printf(" Manufacturer ID: %Xh",J);
printf(" [%s]\n",P? P:"?");
printf(" Version Number: %Xh\n",RAM[0x014C]);
printf(" Complement Check: %Xh\n",RAM[0x014D]);
printf(" Checksum: %Xh\n",Checksum);
J=((word)RAM[0x0103]<<8)+RAM[0x0102];
printf(" Start Address: %Xh\n",J);
}
Checksum+=RAM[0x014E]+RAM[0x014F];
for(I=0;I<0x4000;I++) Checksum-=RAM[I];
if(Verbose) printf("Loading %dx16kB ROM banks:\n.",ROMBanks);
for(I=1;I<ROMBanks;I++)
if(ROMMap[I]=malloc(0x4000))
if(fread(ROMMap[I],1,0x4000,F)==0x4000)
{
for(J=0;J<0x4000;J++) Checksum-=ROMMap[I][J];
if(Verbose) putchar('.');
}
else { if(Verbose) puts("READ FAILED");break; }
else { if(Verbose) puts("MALLOC FAILED");break; }
fclose(F);if(I<ROMBanks) return(0);
if(CheckCRC&&(Checksum&0xFFFF))
{ puts("\nError loading cartridge: Checksum is wrong");return(0); }
if(Verbose) puts("OK");
if(RAMBanks&&!MBCType)
{
if(Verbose) printf("Allocating %dx8kB RAM banks...",RAMBanks);
for(I=0;I<RAMBanks;I++)
if(RAMMap[I]=malloc(0x2000))
memset(RAMMap[I],0,0x2000);
else
{ if(Verbose) puts("FAILED");return(0); }
if(Verbose) puts("OK");
}
if((RAM[0x0147]!=3)&&(RAM[0x0147]!=6)) SaveName=NULL;
else
if(SaveName=malloc(strlen(CartName)+10))
{
strcpy(SaveName,CartName);
if(P=strrchr(SaveName,'.')) strcpy(P,".sav");
else strcat(SaveName,".sav");
if(Verbose) printf("Opening %s...",SaveName);
if(F=fopen(SaveName,"rb"))
{
if(Verbose) printf("reading...");
J=RAM[0x0147]==3? 0x2000:0x0200;
J=(fread(RAMMap[0]? RAMMap[0]:Page[5],1,J,F)==J);
if(Verbose) puts(J? "OK":"FAILED");
fclose(F);
}
else if(Verbose) puts("FAILED");
}
if(CheatCount>0)
{
if(Verbose) puts("Patching cheats into the ROM code:");
for(J=0;J<CheatCount;J++)
{
A=Cheats[J].Address;
if(Verbose)
printf(" at %Xh: %Xh -> %Xh\n",A,Cheats[J].Orig,Cheats[J].Value);
if(A<0x4000)
{ if(ROMMap[0][A]==Cheats[J].Orig) ROMMap[0][A]=Cheats[J].Value; }
else
for(I=0,A-=0x4000;I<ROMBanks;I++)
if(ROMMap[I][A]==Cheats[J].Orig) ROMMap[I][A]=Cheats[J].Value;
}
}
if(SndName)
{
if(Verbose) printf("Logging soundtrack to %s...",SndName);
SndStream=fopen(SndName,"wb");
if(Verbose) puts(SndStream? "OK":"FAILED");
if(SndStream) fprintf(SndStream,"GameBoy Soundtrack File 1.0\032");
}
if(ROMBanks<3) ROMMask=0;
else { for(I=1;I<ROMBanks;I<<=1);ROMMask=I-1;ROMBank=1; }
if(!RAMMap[0]) RAMMask=0;
else { for(I=1;I<RAMBanks;I<<=1);RAMMask=I-1;RAMBank=0; }
if(RAMMap[0]) Page[5]=RAMMap[0];
if(ROMMap[1]) Page[2]=ROMMap[1];
Page[3]=Page[2]+0x2000;
IPeriod=VPeriod/154/11;
TStep=32768;TCount=0;
ChrGen=RAM+0x8800;BgdTab=WndTab=RAM+0x9800;
LCDCONT=0x81;LCDSTAT=0x00;
CURLINE=0x00;CMPLINE=0xFF;
IFLAGS=ISWITCH=0x00;
TIMECNT=TIMEMOD=0x01;
TIMEFRQ=0xF8;
SIODATA=0x00;
SIOCONT=0x7E;
SIOBuf=SIOFlag=0x00;
SprFlag=0;
JoyState=0xFF;
for(I=0;I<4;I++) SPal0[I]=SPal1[I]=BPal[I]=I;
BGRDPAL=SPR0PAL=SPR1PAL=0xE4;
if(Verbose) printf("RUNNING ROM CODE...\n");
ResetZ80(&R);A=Z80(R);
if(Verbose) printf("EXITED at PC = %04Xh.\n",A);
return(1);
}
void TrashGB(void)
{
FILE *F;
int I;
if(SaveName)
{
if(Verbose) printf("\nOpening %s...",SaveName);
if(F=fopen(SaveName,"wb"))
{
if(Verbose) printf("writing...");
I=RAM[0x0147]==3? 0x2000:0x0200;
I=(fwrite(RAMMap[0]? RAMMap[0]:Page[5],1,I,F)==I);
if(Verbose) puts(I? "OK":"FAILED");
fclose(F);
}
else if(Verbose) puts("FAILED");
free(SaveName);
}
if(RAM) free(RAM);
for(I=1;ROMMap[I];I++) free(ROMMap[I]);
for(I=0;RAMMap[I];I++) free(RAMMap[I]);
if(SndName&&SndStream) fclose(SndStream);
}
word Interrupt(void)
{
static byte LCDStates[11] = { 2,2,3,3,3,3,0,0,0,0,0 };
static byte LCount=0;
static byte UCount=1;
static byte ACount=0;
register byte J,I;
DIVREG++;LCount=(LCount+1)%11;
if(CURLINE<144) LCDSTAT=(LCDSTAT&0xFC)|LCDStates[LCount];
switch(LCount)
{
case 0:
/* Proceeding with line refresh */
break;
case 2:
/* Generating VBlank interrupt */
if(CURLINE==144)
if((ISWITCH&VBL_IFLAG)&&(LCDCONT&0x80))
{ IFLAGS=IMask=VBL_IFLAG;return(0x0040); }
return(0xFFFF);
case 7:
/* Generating HBlank interrupt */
if((LCDSTAT&0x08)&&(CURLINE<144))
if((ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80))
{ IFLAGS=IMask=LCD_IFLAG;return(0x0048); }
return(0xFFFF);
default:
/* Only LCD state had to be changed */
return(0xFFFF);
}
CURLINE=(CURLINE+1)%154;
if(!UCount&&(CURLINE<144)) RefreshLine(CURLINE);
SprFlag|=LCDCONT&0x02;
/* Checking for line coincidence */
J=LineDelay? (CURLINE+1)%154:CURLINE;
if(J!=CMPLINE) { LCDSTAT&=0xFB;J=0x00; }
else { LCDSTAT|=0x04;J=0x40; }
/* If end of frame reached... */
if(CURLINE==144)
{
/* Set VBlank state */
LCDSTAT=(LCDSTAT&0xFC)|0x01;J|=0x10;
/* Write interrupt timestamp into sound log */
if(SndStream) fputc(0xFF,SndStream);
/* Refresh screen if needed */
if(UCount) UCount--;
else
{
if(SprFlag&&(LCDCONT&0x80)) RefreshSprites();
RefreshScreen();SprFlag=0;UCount=UPeriod;
}
/* Generate serial IO interrupt */
if(SIOFlag&0x01)
{
SIOFlag&=0xFE;SIOCONT&=0x7F;
if(ISWITCH&SIO_IFLAG) IFLAGS|=SIO_IFLAG;
}
/* Update joystick */
JoyState=Joystick();
/* Autofire emulation */
ACount=(ACount+1)&0x07;
if(ACount>3)
{
if(AutoA) JoyState|=0x10;
if(AutoB) JoyState|=0x20;
}
/* Assign value to JOYPAD */
I=JOYPAD|0xCF;
if(!(I&0x10)) JOYPAD=I&(JoyState|0xF0);
if(!(I&0x20)) JOYPAD=I&((JoyState>>4)|0xF0);
}
/* Generating LCD controller interrupt */
if((J&LCDSTAT)&&(ISWITCH&LCD_IFLAG)&&(LCDCONT&0x80)) IFLAGS|=LCD_IFLAG;
/* Generating timer interrupt */
if(TIMEFRQ&0x04)
{
TCount+=TStep;
if(TCount&0xFFFF0000)
{
unsigned long L;
L=TIMECNT+(TCount>>16);
TCount&=0x0000FFFF;
if(L&0xFFFFFF00)
{
TIMECNT=TIMEMOD;
if(ISWITCH&TIM_IFLAG) IFLAGS|=TIM_IFLAG;
}
else TIMECNT=L;
}
}
/* Determining interrupt address */
if(J=IFLAGS)
{
if(J&EXT_IFLAG) { IMask=EXT_IFLAG;return(0x0060); }
if(J&SIO_IFLAG) { IMask=SIO_IFLAG;return(0x0058); }
if(J&TIM_IFLAG) { IMask=TIM_IFLAG;return(0x0050); }
if(J&LCD_IFLAG) { IMask=LCD_IFLAG;return(0x0048); }
if(J&VBL_IFLAG) { IMask=VBL_IFLAG;return(0x0040); }
}
/* No interrupt */
return(0xFFFF);
}
int AddCheat(char *Cheat)
{
static char Digits[]="0123456789ABCDEF";
int X1,X2;
if(CheatCount>=MAXCHEAT) return(0);
else
{
if((Cheat[3]!='-')||(Cheat[7]!='-')) return(0);
X1=strchr(Digits,toupper(Cheat[0]))-Digits;
X2=strchr(Digits,toupper(Cheat[1]))-Digits;
if((X1<0)||(X2<0)) return(0);
else Cheats[CheatCount].Value=X1*16+X2;
X1=strchr(Digits,toupper(Cheat[4]))-Digits;
X2=strchr(Digits,toupper(Cheat[5]))-Digits;
if((X1<0)||(X2<0)) return(0);
else Cheats[CheatCount].Address=X1*16+X2;
X1=strchr(Digits,toupper(Cheat[6]))-Digits;
X2=strchr(Digits,toupper(Cheat[2]))-Digits;
if((X1<0)||(X2<0)) return(0);
else Cheats[CheatCount].Address+=256*((15-X1)*16+X2);
X1=strchr(Digits,toupper(Cheat[10]))-Digits;
X2=strchr(Digits,toupper(Cheat[8]))-Digits;
if((X1<0)||(X2<0)) return(0);
X1=~(16*X2+X1);
X1=((X1>>2)&0x3F)|((X1<<6)&0xC0);
Cheats[CheatCount].Orig=X1^0x45;
if(Cheats[CheatCount].Address>=0x8000) return(0);
CheatCount++;return(1);
}
}